use std::{
    collections::{BTreeMap, BTreeSet},
    sync::{Arc, Mutex},
};

use anyhow::Result;
use n0_future::{time::Duration, StreamExt};
use serde::{Deserialize, Serialize};
use shared::{ChatSender, ChatTicket, NodeId, TerminalInfo as SharedTerminalInfo, TopicId};
use tracing::level_filters::LevelFilter;
use tracing_subscriber_wasm::MakeConsoleWriter;
use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue};
use wasm_streams::ReadableStream;

#[wasm_bindgen]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TerminalInfo {
    session_id: String,
    name: Option<String>,
    position: TerminalPosition,
    size: TerminalSize,
    status: String,
    created_at: u64,
    last_activity: u64,
}

#[wasm_bindgen]
impl TerminalInfo {
    #[wasm_bindgen(getter)]
    pub fn session_id(&self) -> String {
        self.session_id.clone()
    }

    #[wasm_bindgen(getter)]
    pub fn name(&self) -> Option<String> {
        self.name.clone()
    }

    #[wasm_bindgen(getter)]
    pub fn position(&self) -> TerminalPosition {
        self.position
    }

    #[wasm_bindgen(getter)]
    pub fn size(&self) -> TerminalSize {
        self.size
    }

    #[wasm_bindgen(getter)]
    pub fn status(&self) -> String {
        self.status.clone()
    }

    #[wasm_bindgen(getter)]
    pub fn created_at(&self) -> u64 {
        self.created_at
    }

    #[wasm_bindgen(getter)]
    pub fn last_activity(&self) -> u64 {
        self.last_activity
    }
}

#[wasm_bindgen]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct TerminalPosition {
    pub x: i32,
    pub y: i32,
}

#[wasm_bindgen]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct TerminalSize {
    pub rows: u32,
    pub cols: u32,
}

#[wasm_bindgen]
#[derive(Debug, Serialize, Deserialize)]
pub struct WebShareProxyInfo {
    pub local_port: u16,
    pub public_port: u16,
    node_id: String,
    status: String,
    pub created_at: u64,
}

#[wasm_bindgen]
impl WebShareProxyInfo {
    #[wasm_bindgen(getter)]
    pub fn node_id(&self) -> String {
        self.node_id.clone()
    }

    #[wasm_bindgen(getter)]
    pub fn status(&self) -> String {
        self.status.clone()
    }

    pub fn new(
        local_port: u16,
        public_port: u16,
        node_id: String,
        status: String,
        created_at: u64,
    ) -> Self {
        Self {
            local_port,
            public_port,
            node_id,
            status,
            created_at,
        }
    }
}

// Convert from shared to wasm type
impl From<SharedTerminalInfo> for TerminalInfo {
    fn from(shared: SharedTerminalInfo) -> Self {
        Self {
            session_id: shared.session_id,
            name: shared.name,
            position: TerminalPosition {
                x: shared.position.x,
                y: shared.position.y,
            },
            size: TerminalSize {
                rows: shared.size.rows,
                cols: shared.size.cols,
            },
            status: shared.status,
            created_at: shared.created_at,
            last_activity: shared.last_activity,
        }
    }
}

#[wasm_bindgen(start)]
fn start() {
    console_error_panic_hook::set_once();

    tracing_subscriber::fmt()
        .with_max_level(LevelFilter::DEBUG)
        .with_writer(
            // To avoide trace events in the browser from showing their JS backtrace
            MakeConsoleWriter::default().map_trace_level_to(tracing::Level::DEBUG),
        )
        // If we don't do this in the browser, we get a runtime error.
        .without_time()
        .with_ansi(false)
        .init();

    tracing::info!("(testing logging) Logging setup");
}

/// Node for chatting over iroh-gossip
#[wasm_bindgen]
pub struct ChatNode(shared::ChatNode);

#[wasm_bindgen]
impl ChatNode {
    /// Spawns a gossip node.
    pub async fn spawn() -> Result<Self, JsError> {
        let inner = shared::ChatNode::spawn(None).await.map_err(to_js_err)?;
        Ok(Self(inner))
    }

    /// Returns the node id of this node.
    pub fn node_id(&self) -> String {
        self.0.node_id().to_string()
    }

    /// Returns information about all the remote nodes this [`Endpoint`] knows about.
    /// TODO: Implement with new IROH API
    pub fn remote_info(&self) -> Vec<JsValue> {
        Vec::new()
    }

    /// Opens a chat.
    pub async fn create(&self, nickname: String) -> Result<Channel, JsError> {
        // let ticket = ChatTicket::new(topic);
        let ticket = ChatTicket::new_random();
        self.join_inner(ticket, nickname).await
    }

    /// Joins a chat.
    pub async fn join(&self, ticket: String, nickname: String) -> Result<Channel, JsError> {
        let ticket = ChatTicket::deserialize(&ticket).map_err(to_js_err)?;
        self.join_inner(ticket, nickname).await
    }

    async fn join_inner(&self, ticket: ChatTicket, nickname: String) -> Result<Channel, JsError> {
        let (sender, receiver) = self.0.join(&ticket, nickname).await.map_err(to_js_err)?;
        let sender = ChannelSender(sender);
        let neighbors = Arc::new(Mutex::new(BTreeSet::new()));
        let neighbors2 = neighbors.clone();
        let receiver = receiver.map(move |event| {
            if let Ok(event) = &event {
                match event {
                    shared::Event::NodeJoined { node_id } => {
                        neighbors2.lock().unwrap().insert(*node_id);
                    }
                    shared::Event::NodeLeft { node_id } => {
                        neighbors2.lock().unwrap().remove(node_id);
                    }
                    _ => {}
                }
            }
            event
                .map_err(|err| JsValue::from(&err.to_string()))
                .map(|event| serde_wasm_bindgen::to_value(&event).unwrap())
        });
        let receiver = ReadableStream::from_stream(receiver).into_raw();

        // Add ourselves to the ticket.
        let mut ticket = ticket;
        ticket.bootstrap.insert(self.0.node_id());
        // ticket.bootstrap = [self.0.node_id()].into_iter().collect();

        let topic = Channel {
            topic_id: ticket.topic_id,
            bootstrap: ticket.bootstrap,
            neighbors,
            me: self.0.node_id(),
            sender,
            receiver,
        };
        Ok(topic)
    }
}

type ChannelReceiver = wasm_streams::readable::sys::ReadableStream;

#[wasm_bindgen]
#[derive(Clone)]
pub struct Channel {
    topic_id: TopicId,
    me: NodeId,
    bootstrap: BTreeSet<NodeId>,
    neighbors: Arc<Mutex<BTreeSet<NodeId>>>,
    sender: ChannelSender,
    receiver: ChannelReceiver,
}

#[wasm_bindgen]
impl Channel {
    #[wasm_bindgen(getter)]
    pub fn sender(&self) -> ChannelSender {
        self.sender.clone()
    }

    #[wasm_bindgen(getter)]
    pub fn receiver(&mut self) -> ChannelReceiver {
        self.receiver.clone()
    }

    pub fn ticket(&self, opts: JsValue) -> Result<String, JsError> {
        let opts: TicketOpts = serde_wasm_bindgen::from_value(opts)?;
        let mut ticket = ChatTicket::new(self.topic_id);
        if opts.include_myself {
            ticket.bootstrap.insert(self.me);
        }
        if opts.include_bootstrap {
            ticket.bootstrap.extend(self.bootstrap.iter().copied());
        }
        if opts.include_neighbors {
            let neighbors = self.neighbors.lock().unwrap();
            ticket.bootstrap.extend(neighbors.iter().copied())
        }
        tracing::info!("opts {:?} ticket {:?}", opts, ticket);
        Ok(ticket.serialize())
    }

    pub fn id(&self) -> String {
        self.topic_id.to_string()
    }

    pub fn neighbors(&self) -> Vec<String> {
        self.neighbors
            .lock()
            .unwrap()
            .iter()
            .map(|x| x.to_string())
            .collect()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerInfo {
    pub node_id: NodeId,
    pub nickname: String,
    pub last_active: Duration,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TicketOpts {
    pub include_myself: bool,
    pub include_bootstrap: bool,
    pub include_neighbors: bool,
}

#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct ChannelSender(ChatSender);

#[wasm_bindgen]
impl ChannelSender {
    pub async fn broadcast(&self, text: String) -> Result<(), JsError> {
        self.0.send(text).await.map_err(to_js_err)?;
        Ok(())
    }

    pub fn set_nickame(&self, nickname: String) {
        self.0.set_nickname(nickname);
    }

    // Terminal management methods
    pub async fn create_terminal(
        &self,
        session_id: String,
        shell_path: Option<String>,
        name: Option<String>,
    ) -> Result<(), JsError> {
        self.0
            .create_terminal(session_id, shell_path, name)
            .await
            .map_err(to_js_err)?;
        Ok(())
    }

    pub async fn send_terminal_input(
        &self,
        session_id: String,
        data: Vec<u8>,
    ) -> Result<(), JsError> {
        self.0
            .send_terminal_input(session_id, data)
            .await
            .map_err(to_js_err)?;
        Ok(())
    }

    pub async fn resize_terminal(
        &self,
        session_id: String,
        rows: u32,
        cols: u32,
    ) -> Result<(), JsError> {
        self.0
            .resize_terminal(session_id, rows, cols)
            .await
            .map_err(to_js_err)?;
        Ok(())
    }

    pub async fn close_terminal(&self, session_id: String) -> Result<(), JsError> {
        self.0.close_terminal(session_id).await.map_err(to_js_err)?;
        Ok(())
    }

    // Optimized terminal state synchronization
    pub async fn send_terminal_state(&self, terminals: Vec<TerminalInfo>) -> Result<(), JsError> {
        let shared_terminals: Vec<SharedTerminalInfo> = terminals
            .into_iter()
            .map(|terminal| SharedTerminalInfo {
                session_id: terminal.session_id,
                name: terminal.name,
                position: shared::TerminalPosition {
                    x: terminal.position.x,
                    y: terminal.position.y,
                },
                size: shared::TerminalSize {
                    rows: terminal.size.rows,
                    cols: terminal.size.cols,
                },
                status: terminal.status,
                created_at: terminal.created_at,
                last_activity: terminal.last_activity,
            })
            .collect();

        self.0
            .send_terminal_state(shared_terminals)
            .await
            .map_err(to_js_err)?;
        Ok(())
    }

    pub async fn rename_terminal(
        &self,
        session_id: String,
        new_name: Option<String>,
    ) -> Result<(), JsError> {
        self.0
            .rename_terminal(session_id, new_name)
            .await
            .map_err(to_js_err)?;
        Ok(())
    }

    // WebShare functionality
    pub async fn send_web_share(&self, local_port: u16, public_port: u16) -> Result<(), JsError> {
        self.0
            .send_web_share(local_port, public_port)
            .await
            .map_err(to_js_err)?;
        Ok(())
    }

    pub async fn send_web_unshare(&self, public_port: u16) -> Result<(), JsError> {
        self.0
            .send_web_unshare(public_port)
            .await
            .map_err(to_js_err)?;
        Ok(())
    }

    pub async fn send_web_share_state(
        &self,
        proxies: Vec<WebShareProxyInfo>,
    ) -> Result<(), JsError> {
        let shared_proxies: Vec<shared::WebShareProxyInfo> = proxies
            .into_iter()
            .map(|proxy| {
                let node_id = proxy
                    .node_id
                    .parse::<shared::NodeId>()
                    .unwrap_or_else(|_| shared::NodeId::from_bytes(&[0; 32]).unwrap());
                shared::WebShareProxyInfo {
                    local_port: proxy.local_port,
                    public_port: proxy.public_port,
                    node_id,
                    status: proxy.status,
                    created_at: proxy.created_at,
                }
            })
            .collect();

        self.0
            .send_web_share_state(shared_proxies)
            .await
            .map_err(to_js_err)?;
        Ok(())
    }
}

/// Workspace for managing multiple chat channels
#[wasm_bindgen]
pub struct Workspace {
    node: ChatNode,
    channels: Arc<Mutex<BTreeMap<TopicId, Channel>>>,
}

#[wasm_bindgen]
impl Workspace {
    /// Creates a new workspace
    pub async fn new() -> Result<Workspace, JsError> {
        let node = ChatNode::spawn().await?;
        Ok(Self {
            node,
            channels: Arc::new(Mutex::new(BTreeMap::new())),
        })
    }

    /// Returns the node id
    pub fn node_id(&self) -> String {
        self.node.node_id()
    }

    /// Creates a new channel
    pub async fn create_channel(&self, nickname: String) -> Result<String, JsError> {
        let channel = self.node.create(nickname).await?;
        let topic_id = channel.id();
        let mut channels = self.channels.lock().unwrap();
        channels.insert(topic_id.parse().unwrap(), channel);
        Ok(topic_id)
    }

    /// Joins an existing channel
    pub async fn join_channel(&self, ticket: String, nickname: String) -> Result<String, JsError> {
        let channel = self.node.join(ticket, nickname).await?;
        let topic_id = channel.id();
        let mut channels = self.channels.lock().unwrap();
        channels.insert(topic_id.parse().unwrap(), channel);
        Ok(topic_id)
    }

    /// Leaves a channel
    pub fn leave_channel(&self, topic_id: String) -> Result<(), JsError> {
        let topic_id: TopicId = topic_id.parse().map_err(to_js_err)?;
        let mut channels = self.channels.lock().unwrap();
        channels.remove(&topic_id);
        Ok(())
    }

    /// Gets a channel by ID
    pub fn get_channel(&self, topic_id: String) -> Result<Channel, JsError> {
        let topic_id: TopicId = topic_id.parse().map_err(to_js_err)?;
        let channels = self.channels.lock().unwrap();
        channels
            .get(&topic_id)
            .cloned()
            .ok_or_else(|| JsError::new("Channel not found"))
    }

    /// Lists all active channel IDs
    pub fn list_channels(&self) -> Vec<String> {
        let channels = self.channels.lock().unwrap();
        channels.keys().map(|id| id.to_string()).collect()
    }

    /// Gets the number of active channels
    pub fn channel_count(&self) -> usize {
        let channels = self.channels.lock().unwrap();
        channels.len()
    }

    /// Leaves all channels
    pub fn leave_all_channels(&self) {
        let mut channels = self.channels.lock().unwrap();
        channels.clear();
    }
}

fn to_js_err(err: impl Into<anyhow::Error>) -> JsError {
    let err: anyhow::Error = err.into();
    JsError::new(&err.to_string())
}
